上一篇透過 Deployment、Service、ConfigMap 和 Secret,建立了一個簡單的 Web API,並成功將應用程式部署到 Kubernetes。
不過,目前的應用程式還有兩個限制:
這篇將延續 DAY27 的專案,加入資料讀寫功能,並使用 PVC 保存資料。最後透過 Ingress,讓使用者可以使用 HTTP 路徑存取應用程式。
首先修改 DAY27 建立的 app.py,加入簡單的資料讀寫功能。
為了方便實作,這裡使用 JSON 檔案保存資料,不額外安裝資料庫。
from flask import Flask, jsonify, request
import os
import json
app = Flask(__name__)
DATA_DIR = "/data"
DATA_FILE = os.path.join(DATA_DIR, "messages.json")
os.makedirs(DATA_DIR, exist_ok=True)
def load_messages():
if not os.path.exists(DATA_FILE):
return []
with open(DATA_FILE, "r", encoding="utf-8") as f:
return json.load(f)
def save_messages(messages):
with open(DATA_FILE, "w", encoding="utf-8") as f:
json.dump(messages, f, ensure_ascii=False)
@app.route("/")
def index():
return jsonify({
"message": os.getenv("APP_MESSAGE", "Hello Kubernetes"),
"version": "2.0"
})
@app.route("/config")
def config():
return jsonify({
"app_name": os.getenv("APP_NAME", "demo"),
"secret_configured": bool(os.getenv("APP_SECRET"))
})
@app.route("/messages", methods=["GET"])
def get_messages():
return jsonify(load_messages())
@app.route("/messages", methods=["POST"])
def add_message():
data = request.get_json(silent=True)
if not isinstance(data, dict):
return jsonify({"error": "Invalid JSON"}), 400
message = data.get("message")
if not isinstance(message, str) or not message.strip():
return jsonify({"error": "Invalid message"}), 400
messages = load_messages()
messages.append(message)
save_messages(messages)
return jsonify({
"status": "success",
"message": message
}), 201
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
這次新增了兩個 API:
| Method | Path | 功能 |
|---|---|---|
| GET | /messages |
讀取已保存的訊息 |
| POST | /messages |
新增訊息 |
資料會儲存在 /data/messages.json。
不過,容器本身的檔案系統並不適合用來保存需要長期保留的資料。當 Pod 被重新建立時,原本寫在容器可寫層中的資料通常不會保留。
因此,接下來使用 PVC 提供持久化儲存空間。
這個 JSON 範例只用來示範持久化儲存,不適合多個程序同時寫入,也沒有完整的資料庫交易保護。正式應用程式應考慮使用資料庫。
DAY11 已經介紹過 PV 與 PVC 的基本概念。
這次透過 PVC 向 Kubernetes 申請儲存空間,再將它掛載到容器的 /data 目錄。
建立 k8s/pvc.yaml:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: final-demo-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
這裡設定:
ReadWriteOnce:允許 Volume 以讀寫模式掛載到單一 Node。storage: 1Gi:申請 1Gi 的儲存空間。這份 YAML 沒有指定 storageClassName,因此會使用 Cluster 的預設 StorageClass。
可以先查看:
kubectl get storageclass
確認是否存在預設 StorageClass。
ReadWriteOnce 並不代表只能有一個 Pod 使用 Volume,而是限制 Volume 的讀寫掛載節點。為了避免這次簡單的 JSON 檔案發生並行寫入問題,我們會將 Deployment 調整成一個副本。
接著修改 k8s/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: final-demo
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: final-demo
template:
metadata:
labels:
app: final-demo
spec:
containers:
- name: web
image: k8s-final-demo:2.0
imagePullPolicy: Never
ports:
- containerPort: 5000
envFrom:
- configMapRef:
name: final-demo-config
- secretRef:
name: final-demo-secret
volumeMounts:
- name: app-data
mountPath: /data
volumes:
- name: app-data
persistentVolumeClaim:
claimName: final-demo-pvc
與上一篇相比,這次增加了:
replicas: 1:使用單一 Pod,避免 JSON 檔案被多個副本同時寫入。strategy.type: Recreate:更新時先停止舊 Pod,再建立新 Pod。volumeMounts:將儲存空間掛載到 /data。volumes:指定使用 final-demo-pvc。透過這樣的設定,容器即使被重新建立,只要 PVC 與底層儲存資料仍然存在,就能繼續讀取原本保存的資料。
由於修改了 app.py,需要重新建立 Docker 映像檔。
在專案根目錄執行:
docker build -t k8s-final-demo:2.0 .
接著載入 Minikube:
minikube image load k8s-final-demo:2.0
如果使用多節點 Minikube,需要確保 Pod 被排程到的 Node 能取得這個映像檔。
先建立 PVC:
kubectl apply -f k8s/pvc.yaml
接著更新 Deployment:
kubectl apply -f k8s/deployment.yaml
查看 PVC:
kubectl get pvc
預期 PVC 會進入 Bound 狀態,代表已成功綁定儲存空間。
部分 StorageClass 使用 WaitForFirstConsumer,可能需要等到 Pod 排程後才完成綁定。
接著查看 Pod:
kubectl get pods
確認新的 Pod 正常啟動。
首先使用 port-forward 存取 Service:
kubectl port-forward svc/final-demo-service 8080:80
接著開啟另一個 PowerShell 視窗,新增一筆資料:
Invoke-RestMethod `
-Uri http://localhost:8080/messages `
-Method POST `
-ContentType "application/json" `
-Body '{"message":"Hello PVC"}'
會回傳:
接著讀取資料:
Invoke-RestMethod http://localhost:8080/messages
會看到:
代表應用程式已成功將資料寫入掛載的儲存空間。
接下來手動刪除目前的 Pod,觀察 Kubernetes 是否能重新建立 Pod,並保留資料。
先查看 Pod:
kubectl get pods
取得 Pod 名稱後執行:
kubectl delete pod <pod-name>
由於 Deployment 設定了 replicas: 1,Kubernetes 會自動建立新的 Pod,維持指定的副本數量。
等待新的 Pod 啟動:
kubectl get pods -w
新的 Pod 啟動後,由於原本的 kubectl port-forward 連線可能隨著舊 Pod 被刪除而中斷,因此需要重新建立連線:
kubectl port-forward svc/final-demo-service 8080:80
接著在另一個 PowerShell 視窗再次讀取資料:
Invoke-RestMethod http://localhost:8080/messages
如果仍然看到:
[
"Hello PVC"
]
就代表資料並沒有隨著原本的 Pod 一起消失。
這是因為資料儲存在 PVC 所提供的持久化儲存空間,而不是只存在原本容器的可寫層中。
注意:這裡測試的是 Pod 重建後的資料保留,不代表資料已經具備跨 Node、跨 Cluster 的備援能力。實際資料可用性仍取決於底層 StorageClass 和儲存系統。
前面已經使用 Service 提供 Cluster 內部存取。
接下來透過 Ingress,讓 HTTP 請求可以依照設定的路徑轉送到 Service。
首先確認 Minikube 已啟用 Ingress Controller:
minikube addons enable ingress
查看 Ingress Controller 是否正常執行:
kubectl get pods -n ingress-nginx
接著建立 k8s/ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: final-demo-ingress
spec:
ingressClassName: nginx
rules:
- host: final-demo.test
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: final-demo-service
port:
number: 80
這份 Ingress 會將符合 final-demo.test Host 的 HTTP 請求轉送到 final-demo-service。
建立 Ingress:
kubectl apply -f k8s/ingress.yaml
查看狀態:
kubectl get ingress

建立 Ingress 後,接下來測試 HTTP 請求是否能夠透過 Ingress Controller 轉送到對應的 Service。
由於不同的 Minikube Driver 可能有不同的網路設定,在 Windows 使用 Docker Driver 時,可能無法直接透過 Minikube 的 Node IP 存取 Ingress Controller。
因此,這裡使用 kubectl port-forward 將 Ingress Controller 的 HTTP Port 轉送到本機進行測試。
首先開啟一個 PowerShell 視窗,執行:
kubectl port-forward -n ingress-nginx svc/ingress-nginx-controller 8081:80
正常情況下會顯示:
Forwarding from 127.0.0.1:8081 -> 80
保持這個終端機執行,接著開啟另一個 PowerShell 視窗:
curl.exe -H "Host: final-demo.test" http://127.0.0.1:8081/
這裡透過 -H 指定 HTTP Host,讓 Ingress Controller 根據 final-demo.test 找到對應的 Ingress 規則。
如果成功,會回傳:
也可以測試先前建立的資料讀取 API:
curl.exe -H "Host: final-demo.test" http://127.0.0.1:8081/messages
如果資料仍然存在,會回傳:
[
"Hello PVC"
]
這代表 HTTP 請求已經成功經過:
Windows
|
| kubectl port-forward
v
Ingress Controller
| 根據 Ingress Rule 選擇 Service
v
Service
|
v
Pod
需要注意的是,這次使用 Port Forward 是為了在本機測試 Ingress 的路由功能,並不代表 Ingress 已經能直接從外部網路存取。
實際環境中,可以依照使用的 Kubernetes 環境,透過 NodePort、LoadBalancer 或其他網路設定提供對外存取。
這篇延續 DAY27 建立的 Web API,加入資料持久化功能,並使用 Ingress 提供 HTTP 存取。
在這次實作中:
透過刪除 Pod 並重新讀取資料,也驗證了持久化儲存的基本效果。
下一篇將繼續使用這個專案,加入健康檢查與資源限制,並透過故障模擬觀察 Kubernetes 的維運機制。